<?php

abstract class Module_Exception extends CMS_Exception {
	private $_module;
	public function __construct($module){
		$this->_module = $module;
		parent::__construct();
	}
	public function __toString(){
		$class = is_object($this->_module) ? get_class($this->_module) : $this->_module;
		return "{$class}\t" . parent::__toString();
	}
}
class Module_Invalid_Exception extends Module_Exception {}
abstract class Module_Class_Exception extends Module_Exception {}
class Module_Class_Invalid_Exception extends Module_Class_Exception {}
class Module_Class_Not_Found_Exception extends Module_Class_Exception {}
class Module_Section_Exception extends Module_Exception {
	private $_section;
	public function __construct($module, $section){
		$this->_section = $section;
		parent::__construct($module);
	}
}
class Module_Section_Not_Found_Exception extends Module_Section_Exception {
	private $_child;
	public function __construct($module, $section, $child){
		$this->_child = $child;
		parent::__construct($module, $section);
	}
	public function getExceptionTrace(){
		return $this->getCode() . " " . $this->_child->getCode();
	}
}
abstract class Module_Section_Class_Exception extends Module_Section_Exception {
	private $_class;
	public function __construct($module, $section, $class){
		$this->_class = $class;
		parent::__construct($module, $section);
	}
}
class Module_Section_Class_Not_Found_Exception extends Module_Section_Class_Exception {}
class Module_Section_Class_Invalid_Exception extends Module_Section_Class_Exception {}
class Module_Not_Found_Exception extends Module_Exception {
	protected $_child;

	public function getExceptionTrace(){
		return parent::getExceptionTrace() . (isset($this->_child) ? " " . $this->_child->getExceptionTrace() : "");
	}

	public function __toString(){
		return (isset($this->_child) ? $this->_child : '') . parent::__toString();
	}

	public function __construct($message, $exception = null){
		if(isset($exception)){
			$this->_child = $exception;
		}
		parent::__construct($message);
	}
}

abstract class Module {

	protected $id;
	protected $name;
	protected $location;
	protected $version;
	protected $site;
	protected $class;

	private static $modules;
	private static $mdata;

	protected function __construct($data = array()){
		foreach($data as $k => $v){
			$this->$k = $v;
		}
	}

	public static function Get($name){
		if(!isset(Module::$modules[$name])){
			throw new Module_Not_Found_Exception($name);
		}
		return Module::$modules[$name];
	}

	public function Load_Section($section){
		try {
			CMS::File("{$this->location}/{$section}");
			$class = "{$this->location}_{$section}";
			if(!class_exists($class)){
				throw new Module_Section_Class_Not_Found_Exception($this, $section, $class);
			}
			if(!is_subclass_of($class, $section)){
				throw new Module_Section_Class_Invalid_Exception($this, $section, $class);
			}
			return $class;
		} catch (CMS_File_Exception $e){
			throw new Module_Section_Not_Found_Exception($this, $section, $e);
		}
	}

	// Dependancy Solution Solver!
	public static function LoadModule($name){
		if(isset(Module::$modules[$name])){
			// Already loaded
			return;
		}
		if(!isset(Module::$mdata[$name])){
			throw new Module_Not_Found_Exception($name);
			// Not going to be loaded.
		}

		// Attempt to load out of sequence

		try {
			Module::$modules[$name] = call_user_func(array(Module::$mdata[$name]['class'], 'Load'), Module::$mdata[$name]);
		} catch (Exception $e){
			// Module failed to load!
			unset(Module::$mdata[$name]);	// Clear Mdata
			unset(Module::$modules[$name]); // Clear Module
			throw new Module_Not_Found_Exception($name, $e);
		}
	}	

	public static function Load($data = null){
		if(isset($data)){
			if(!isset($data['class'])){ 
				throw new Module_Invalid_Exception("Undefined module data element 'class'", $data);
			}	
			$class = $data['class'];
			return new $class($data);
		}

		$result = Database::getInstance()->query('SELECT * from `modules` WHERE `site` = %u', Site::getInstance()->getID());
		Module::$mdata = array();
		while($module =$result->fetch_assoc()){
			try {
				CMS::File("{$module['location']}/module");
				$module['class'] = "Module_{$module['location']}";
				if(!class_exists($module['class'])){
					throw new Module_Class_Not_Found_Exception($module['name'],$module['location']);
				}
				if(!is_subclass_of($module['class'], 'Module')){
					throw new Module_Class_Invalid_Exception($module['location']);
				}
				Module::$mdata[$module['location']] = $module;
			} catch (Exception $e){
				unset(Module::$mdata[$module['location']]);
			}
		}

		// Mdata is now loaded, let's proceed to load modules. Order is solved by dependancy solver.

		$modules = array_keys(Module::$mdata); // Grab list of modules to process

		Module::$modules = array();

		foreach($modules as $module){
			try {
				Module::LoadModule($module);
			} catch (Exception $e){

			}
		}

	}

}
